﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.Contracts;
using System.IO;
using System.Text;

namespace OpenLS.Spreadsheet.Formats.Biff.Records
{
    internal class XLUnicodeRichExtendedString
    {
        private bool fHighByte;
        private string value;
        private List<FormatRun> runs = new List<FormatRun>();
        private List<ExtRst> ExtRsts = new List<ExtRst>();

        public static explicit operator string(XLUnicodeRichExtendedString item)
        {
            return item.value;
        }

        public int Length
        {
            get
            {
                int result = 0;
                result += 2;
                result += 1;
                if (runs.Count > 0)
                {
                    result += 2;
                }
                if (this.ExtRsts.Count > 0)
                {
                    result += 2;
                }
                if (fHighByte)
                {
                    result += value.Length*2;
                }
                else
                {
                    result += value.Length;
                }
                foreach (var item in this.runs)
                    result += FormatRun.Length;
                foreach (var item in this.ExtRsts)
                    result += item.Length;
                return result;
            }
        }

        public override int GetHashCode()
        {
            int result = value.GetHashCode();
            result += Enumerable.Sum(runs, r => r.GetHashCode());
            result += Enumerable.Sum(ExtRsts, r => r.GetHashCode());
            return result;
        }
        public  bool Equals(XLUnicodeRichExtendedString other)
        {
            if (!object.Equals(value, other.value))
                return false;
            if (runs.Count != other.runs.Count)
                return false;
            if (ExtRsts.Count != other.ExtRsts.Count)
                return false;
            for (int i = 0; i < runs.Count; i++)
            {
                if (!runs[i].Equals(other.runs[i]))
                    return false;
            }
            for (int i = 0; i < ExtRsts.Count; i++)
            {
                if (!ExtRsts[i].Equals(other.ExtRsts[i]))
                    return false;
            }
            return true;
        }
        public override bool Equals(object obj)
        {
            if (obj == this)
                return true;
            XLUnicodeRichExtendedString xlUnicodeRichExtendedString = obj as XLUnicodeRichExtendedString;
            if (xlUnicodeRichExtendedString == null)
                return false;
            return Equals((xlUnicodeRichExtendedString));
        }
        public static explicit operator XLUnicodeRichExtendedString(string item)
        {
            Contract.Requires(item != null);
            return new XLUnicodeRichExtendedString() {value = item};
        }

        public static XLUnicodeRichExtendedString Read(MultiBinaryReader reader)
        {
            XLUnicodeRichExtendedString result = new XLUnicodeRichExtendedString();
            result.ReadThis(reader);
            return result;
        }

        private void ReadThis(MultiBinaryReader reader)
        {
            var cch = reader.ReadUInt16();
            Contract.Assert(cch < 10000);
            var abcd = reader.ReadByte();
            this.fHighByte = (abcd & 0x001) != 0;
            var reserved1 = (abcd & 0x002) != 0;
            var fExtSt = (abcd & 0x004) != 0;
            var fRichSt = (abcd & 0x008) != 0;
            ushort cRun = 0;
            if (fRichSt)
            {
                cRun = reader.ReadUInt16();
                //Contract.Assert((cRun < 1000));// sanity test
            }
            uint cbExtRst = 0;
            if (fExtSt)
            {
                cbExtRst = reader.ReadUInt32();
            }
            value = ReadStrings(fHighByte, reader, cch);
            for (int i = 0; i < cRun; i++)
            {
                runs.Add(new FormatRun(reader));
            }
            if (fExtSt)
            {
                for (int i = 0; i < cbExtRst; i++)
                    reader.ReadByte();
                //var throwAway = reader.Reader.ReadBytes((int) cbExtRst);
              /*  
                int countSoFar = 0;
                while (countSoFar <cbExtRst)
                {
                    var e = new ExtRst(reader.Reader);
                    ExtRsts.Add(e);
                    countSoFar += e.Length;
                }
                Contract.Assert(countSoFar == cbExtRst);
                 */
            }
        }
        static string ReadStrings (bool initialHighByte, MultiBinaryReader reader, int totalCount)
        {
            if (totalCount == 0)
                return string.Empty;
            bool fHighByte = initialHighByte; 
            if (reader.Reader.BaseStream.Position == 0)
                fHighByte = reader.ReadByte() != 0;
            var result = ReadString(fHighByte, reader, totalCount);
            while (result.Length < totalCount)
            {
                fHighByte = reader.ReadByte() != 0;
                result += ReadString(fHighByte, reader, totalCount - result.Length);
            }
            return result;
        }
        static string ReadString (bool fHighByte, MultiBinaryReader reader, int cch)
        {
            if (fHighByte)
            {
                byte[] bytes = reader.AttemptReadBytes(cch*2);
                return  XlEncodings.TwoByteEncoding.GetString(bytes);
            }
            else
            {
                byte[] bytes = reader.AttemptReadBytes(cch);
                return  XlEncodings.OneByteEncoding.GetString(bytes);
            }
        }
        Encoding Encoding
        {
            get
            {
                if (fHighByte)
                    return XlEncodings.TwoByteEncoding;
                return XlEncodings.OneByteEncoding;
            }
        }

#if DEBUG
        public override string ToString()
        {
            var result = this.value;
            result += "(" + this.fHighByte + ")";
            foreach (var item in this.runs)
                result += item.ToString();
            return result;
        }
#endif
        public void Write(BinaryWriter writer)
        {
            ushort count = (ushort) value.Length;
            writer.Write(count);
            byte b = 0;
            if (fHighByte)
                b |= 0x01;
            if (ExtRsts.Count > 0)
                b |= 0x04;
            if (this.runs.Count > 0)
                b |= 0x08;
            writer.Write(b);
            if (runs.Count > 0)
            {
                count = (ushort) runs.Count;
                writer.Write(count);
            }
            if (this.ExtRsts.Count > 0)
            {
                count = (ushort)ExtRsts.Count;
                writer.Write(count);
            }
            if (fHighByte)
            {
                writer.Write(Encoding.GetBytes(value));
            }
            else
            {
                writer.Write(Encoding.GetBytes(value));
            }
            foreach (var item in this.runs)
                item.Write(writer);
            foreach (var item in this.ExtRsts)
                item.Write(writer);
        }
    }
}